package edu.yzu.testspringboot002.handler;


import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.springframework.web.socket.WebSocketSession;
import com.fasterxml.jackson.databind.ObjectMapper;


@Component
public class MyWebSocketHandler extends TextWebSocketHandler {

   //construct method
    public MyWebSocketHandler(ObjectMapper objectMapper){
        this.objectMapper=objectMapper;



    }


    private static final Logger LOGGER = LoggerFactory.getLogger(MyWebSocketHandler.class);
    // 用于存储群组信息，键为群组ID，值是GroupInfo对象，这个GroupInfo包含在线用户会话列表和历史消息列表
    private Map<String, GroupInfo> groupInfos = new HashMap<>();
    // 用于存储用户与群组的关联关系，键为用户ID，值为群组ID列表  一个用户可以加入多个群组   它是一个Map，键是用户ID，值是群组ID列表
    private Map<String, List<String>> userGroups = new HashMap<>();

    private Map<String,String> sessionIdToUserIdMap =new HashMap<>(); //存放  sessionId  与  userId 的map


    private ObjectMapper objectMapper;

    private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    //

    public int checkUserIdAndSessionId(String sessionId,String userId ) {
        // 遍历userId_SessionId（虽然在这个场景中并不需要遍历）
        for (Map.Entry<String, String> entry : sessionIdToUserIdMap.entrySet()) {
            String   storedSessionId = entry.getKey();
            String storedUserId = entry.getValue();
            // 检查userId是否存在
            if (storedUserId.equals(userId)) {
                // 如果存在，进一步判断对应的sessionId是否和参数sessionId相等
                if (storedSessionId.equals(sessionId)) {
                    return 0; // 相等返回0
                } else {
                    return 2; // 不等返回2
                }
            }
        }
        // 其他情况返回3（即userId不存在）
        return 3;
    }

    /**
     * afterConnectionEstablished 是一个 WebSocket API 中的回调函数，它是在建立 WebSocket 连接之后被调用的。
     * 当 WebSocket 连接建立成功后，浏览器会发送一个握手请求给服务器端，如果服务器成功地接受了该请求，那么连接就会被建立起来
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        LOGGER.info("WebSocket已连接: {}", session.getId());

        // 假设从WebSocket连接的属性或者请求参数中获取用户ID和群组ID列表（实际需按业务逻辑调整获取方式）
        // 理想情况下 应该是从请求参数中获取userId和groupId   我们在每次连接的时候都会传递userId和groupId   但是但是 我们先不在这里传递，每次收到消息的时候再传递
//        String userId = (String) session.getAttributes().get("userId");
//        List<String> groupIds = (List<String>) session.getAttributes().get("groupIds");
//
//        if (userId!= null && groupIds!= null) {
//            userGroups.put(userId, groupIds);
//            for (String groupId : groupIds) {
//                // 用于存储群组信息，键为群组ID，值包含在线用户会话列表和历史消息列表
//                groupInfos.computeIfAbsent(groupId, k -> new GroupInfo()).addSession(session);
//            }
//        }
       //System.out.println("groupInfos: " + groupInfos);
    }

    /**
     * handleMessage 是 WebSocket API 中的回调函数，它是用来处理从客户端接收到的 WebSocket 消息的。
     * 当客户端通过 WebSocket 连接发送消息到服务器端时，服务器端会自动调用 handleMessage 函数并传递收到的消息作为参数，你可以在该函数中处理这个消息，并根据需要向客户端发送一些响应消息。
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        LOGGER.info("WebSocket收到的消息: {}", message.getPayload());
        // 将消息反序列化，假设消息是JSON格式，这里解析为Message对象（下面定义）
        Message msg = this.objectMapper.readValue(message.getPayload().toString(), Message.class);
        String groupId = msg.getGroupId();
        String userId=msg.getUserId();
        int flag=this.checkUserIdAndSessionId(session.getId(),userId);  //检查userId和sessionId是否匹配
        //flag==0 说明userId和sessionId匹配 ,不做更多的操作 直接执行下面的代码
        if(flag==2){   //userId一致 但是 sessionId不一致,说明用户重新建立了session,要把原来该用户相关的session等全部删除掉
              //找到这个用户下原来所有的session，然后全部删除掉
            for (Map.Entry<String, String> entry : sessionIdToUserIdMap.entrySet()) {
                String storedSessionId = entry.getKey();
                String storedUserId = entry.getValue();
                if (storedUserId.equals(userId)) {
                    sessionIdToUserIdMap.remove(storedSessionId);
                }
                //by userId remove all the groupInfo
                List<String> groupIds = userGroups.get(userId);
                if (groupIds!= null) {
                    for (String groupId1 : groupIds) {
                        GroupInfo groupInfo = groupInfos.get(groupId1);
                        if (groupInfo!= null) {
                            groupInfo.removeSession(session);
                        }
                    }
                    userGroups.remove(userId);
                }
            }

        }else if(flag==3){  //userId不存在  session不存在 ，用户第一次建立连接
          //do nothing   放在这里就是为了说明flag==3的情况
        }

        if(flag==2 || flag==3){
            sessionIdToUserIdMap.put(session.getId(),userId);    //sessionId 与 userId 建立映射  是1对1的关系
            List<String> groupIds = userGroups.computeIfAbsent(userId, k -> new ArrayList<>());  // 获取用户的群组ID列表
            groupIds.add(groupId);
        }

        GroupInfo groupInfo = groupInfos.get(groupId);
        if (groupInfo == null) {
            // 如果群组信息不存在，则创建新的群组信息，并添加当前用户的WebSocketSession
            groupInfo = new GroupInfo();
            groupInfo.addSession(session);
            groupInfos.put(groupId, groupInfo);
            // 同时，假设这里从WebSocket连接的属性或者请求参数中获取用户ID（实际需按业务逻辑调整获取方式）
            //String userId = (String) session.getAttributes().get("userId");
//            List<String> groupIds = userGroups.computeIfAbsent(userId, k -> new ArrayList<>());  // 获取用户的群组ID列表
//            groupIds.add(groupId);
        }else {
            groupInfo.addSession(session);  //addSession()方法会检查是否已经存在，如果存在就不会再添加
        }

        // 将消息添加到群组历史消息列表
        groupInfo.addHistoryMessage(msg);

        // 向群组内所有在线用户发送消息
        List<WebSocketSession> sessions = groupInfo.getSessions();
        for (WebSocketSession s : sessions) {
            try {
                s.sendMessage(new TextMessage(this.objectMapper.writeValueAsString(msg)));
            } catch (IOException e) {
                LOGGER.error("无法发送WebSocket消息", e);
            }
        }
    }


    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {  // 处理文本消息
        handleMessage(session, message);
    }



    @Scheduled(fixedRate = 10000)
    void sendPeriodicMessages() throws IOException {
        // 这里可扩展定时向群组推送系统消息等功能，暂不做详细修改
        for (GroupInfo groupInfo : groupInfos.values()) {
            List<WebSocketSession> sessions = groupInfo.getSessions();
            for (WebSocketSession s : sessions) {
                if (s.isOpen()) {
                    String broadcast = "server periodic message " + LocalDateTime.now();
                    LOGGER.info("Server sends: {}", broadcast);
                    s.sendMessage(new TextMessage(broadcast));
                }
            }
        }
    }


    // 处理用户重新上线，推送历史消息的方法
    public void handleUserReconnect(String userId, WebSocketSession session) {
        List<String> groupIds = userGroups.get(userId);
        if (groupIds!= null) {
            for (String groupId : groupIds) {
                GroupInfo groupInfo = groupInfos.get(groupId);
                if (groupInfo!= null) {
                    List<Message> historyMessages = groupInfo.getHistoryMessages();
                    for (Message historyMessage : historyMessages) {
                        try {
                            session.sendMessage(new TextMessage(this.objectMapper.writeValueAsString(historyMessage)));
                        } catch (IOException e) {
                            LOGGER.error("无法发送历史消息给重新上线用户", e);
                        }
                    }
                }
            }
        }
    }

    /**
     * afterConnectionClosed 是 WebSocket API 中的回调函数，它是在 WebSocket 连接关闭后被调用的。
     * 当客户端或服务器端主动关闭 WebSocket 连接时，afterConnectionClosed 回调函数会被调用，你可以在该函数中执行一些资源释放、清理工作等操作，比如关闭数据库连接、清理缓存等。
     */

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        LOGGER.info("WebSocket已断开连接: {}", session.getId());
        //应该根据sessionid 出发，找到userId，且把该用户名下的所有的GroupInfo  中的 session 全部删除掉
        // 移除用户会话在各个群组中的关联
        //String userId = (String) session.getAttributes().get("userId");
        //query userId by sessionId
        String userId = sessionIdToUserIdMap.get(session.getId());
        List<String> groupIds = userGroups.get(userId);
        if (groupIds!= null) {
            for (String groupId : groupIds) {
                GroupInfo groupInfo = groupInfos.get(groupId);
                if (groupInfo!= null) {
                    groupInfo.removeSession(session);
                }
            }
            userGroups.remove(userId);
        }
    }

    /**
     * supportsPartialMessages 是 WebSocket API 中的方法，它用来指示 WebSocket 消息是否支持分段传输。
     * WebSocket 消息可以分段传输，也就是说一个消息可以被分成多个部分依次传输，这对于大型数据传输和流媒体传输非常有用。当消息被分成多个部分传输时，WebSocket 会自动将这些部分合并成完整的消息。
     * supportsPartialMessages 方法用来指示服务器是否支持分段消息传输，如果支持，则可以在接收到部分消息时开始处理消息，否则需要等待接收到完整消息后才能开始处理。
     */
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * handleTransportError 是 WebSocket API 中的回调函数，它用来处理 WebSocket 传输层出现错误的情况。
     *当 WebSocket 传输层出现错误，比如网络中断、协议错误等，WebSocket 会自动调用 handleTransportError 函数，并传递相应的错误信息。在该函数中，我们可以处理这些错误，比如关闭 WebSocket 连接、记录错误日志等。
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        LOGGER.error("WebSocket错误", exception);
    }



    // 在MyWebSocketHandler类内部定义
    private class GroupInfo {
        // 存储群组内所有在线用户的WebSocketSession列表
        private List<WebSocketSession> sessions = new ArrayList<>();
        // 存储群组的历史消息列表，消息以自定义的Message对象形式存储（前面代码中已定义Message类）
        private List<Message> historyMessages = new ArrayList<>();

        // 添加一个用户的WebSocketSession到群组的在线用户列表中
        public void addSession(WebSocketSession session) {
            if(0==sessions.size()){
                sessions.add(session);
            }else {
                //iterator sessions, if the session is already in the list, do not add it again,else add it
                for (WebSocketSession s : sessions) {
                    if (s.getId().equals(session.getId())) {
                        return;
                    }
                }
                sessions.add(session);
            }
        }

        // 从群组的在线用户列表中移除指定用户的WebSocketSession
        public void removeSession(WebSocketSession session) {
            sessions.remove(session);
        }

        // 向群组的历史消息列表中添加一条消息
        public void addHistoryMessage(Message message) {
            historyMessages.add(message);
        }

        // 获取群组内所有在线用户的WebSocketSession列表
        public List<WebSocketSession> getSessions() {
            return sessions;
        }

        // 获取群组的历史消息列表
        public List<Message> getHistoryMessages() {
            return historyMessages;
        }
    }


    @Data
    // 定义消息类，包含要的消息属性，可根据实际需求扩展
    private static class Message {
        private String type; // 消息类型，如 "text" 或 "image"
        private String groupId; // 群组ID
        private String sender; // 发送者（可根据实际情况完善，比如用户ID等）  //暂时没用 其实 这个名字更准确
        private String content; // 消息内容，文字或图片链接等

//        private Timestamp timestamp;   //发送时间戳


        private String userId; // 新增字段  发送者是谁
        private LocalDateTime timestamp = LocalDateTime.now(); // 时间戳




    }

}

